﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Reflection;
using System.Threading;
using Core;
using Core.ChannelProtocol;
using Core.DeviceProtocol;
using Core.Helper;
using Core.Model;
using Core.Msg;
using log4net;

namespace SerialPortChannel
{
    public sealed class SerialChannel : BaseChannel
    {
        private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        private Thread _channelThread;    // 轮询线程
        private readonly Queue<RxTxCommand> _sendCommands = new Queue<RxTxCommand>();
        private readonly Queue<RxTxCommand> _insertCommands = new Queue<RxTxCommand>();
        
        private SerialPort _comPort;
        private bool _running;
        private bool _connected = true;

        private List<byte> _receiveBuf = new List<byte>();

        public SerialChannel(Channel channel)
        {
            try
            {
                ChlInfo = channel;
                ChlInfo.PollingDelay = channel.PollingDelay < 5 ? 5 : channel.PollingDelay;
                _comPort = new SerialPort();
                _comPort.PortName = ChlInfo.Comport;
                _comPort.BaudRate = ChlInfo.Baudrate;
                _comPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), ChlInfo.StopBits);
                _comPort.DataBits = ChlInfo.DataBits;
                _comPort.Parity = (Parity)Enum.Parse(typeof(Parity), ChlInfo.Parity);
                _comPort.ReadTimeout = ChlInfo.ReadTimeout;
                _comPort.WriteTimeout = ChlInfo.WriteTimeout;
                _comPort.DataReceived += Comport_DataReceived;
                List<Device> devs = Global.DataAccess.GetDevices(ChlInfo.Id);
                if (devs != null)
                {
                    foreach (Device dev in devs)
                    { 
                        LoadProtocol(dev);
                    }
                }
            }
            catch
            {
                
            }
        }

        private void Comport_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            byte[] recBytelist = new byte[_comPort.BytesToRead];
            int readCount = 0;
            try
            {
                readCount = _comPort.Read(recBytelist, 0, _comPort.BytesToRead);
            }
            catch
            {
                if (readCount <= 0 || readCount > 3000)
                {
                    return;
                }
            }
            _receiveBuf.AddRange(recBytelist);
        }

        private void ClosePort()
        {
            try
            {
                if (_comPort!=null)
                {
                    if(_comPort.IsOpen)
                        _comPort.Close();
                    _comPort.DataReceived -= Comport_DataReceived;
                }
                _comPort = null;
            }
            catch
            {
            }
        }
        private void ThreadProc()
        {
            while (Running)
            {
                if (_sendCommands.Count > 0 || _insertCommands.Count > 0)
                {
                    if (!Connected)
                    {
                        if (!Connect())
                        {
                            Thread.Sleep(5000);
                            _sendCommands.Clear();
                            _insertCommands.Clear();
                            continue;
                        }
                    }
                    try
                    {
                        RxTxCommand cmd;
                        if (_insertCommands.Count > 0)
                        {
                            cmd = _insertCommands.Dequeue();
                        }
                        else
                        {
                            cmd = _sendCommands.Dequeue();
                        }

                        if (cmd != null && Connected)
                        {
                            cmd.SendTime = DateTime.Now;
                            _receiveBuf.Clear();
                            SendData(cmd);
                            if (cmd.NeedReply)
                            {
                                Thread.Sleep(ChlInfo.PollingDelay);
                                if (cmd.Timeout > 5)
                                    Thread.Sleep(cmd.Timeout);
                                cmd.ReceiveBytes = _receiveBuf.ToArray();//ReadData();
                                _receiveBuf.Clear();
                                if (cmd.ReceiveResult == MsgResult.RecTimeout || cmd.ReceiveResult== MsgResult.Undefined)
                                {
                                    if (CurDevProtocol != null && CurDevProtocol.Dev.Id == cmd.DeviceId)
                                    {
                                        if (CurDevProtocol.Dev.BreakCount < CurDevProtocol.Dev.BreakOff)
                                        {
                                            CurDevProtocol.Dev.BreakCount++;
                                            if (CurDevProtocol.Dev.BreakCount == CurDevProtocol.Dev.BreakOff)
                                            {
                                                CurDevProtocol.DataItems.ForEach(sss=>sss.CurValue=0);
                                            }
                                        } 
                                    }
                                }
                                else
                                {
                                    if (CurDevProtocol != null && CurDevProtocol.Dev.Id == cmd.DeviceId)
                                    {
                                        CurDevProtocol.Dev.BreakCount=0;
                                    }
                                }
                            }
                            RaiseMessageEvent(cmd);
                        }
                    }
                    catch (Exception ex)
                    {
                        _log.Info(ex.Message);
                    }
                }
                else
                {
                    NextDevice();
                    CurDevProtocol?.RunPolling();
                    Thread.Sleep(5);
                }
               
            }
            ClosePort();
        }

       
        public override bool Connected
        {
            get
            {
                bool temp = (_comPort != null && _comPort.IsOpen);
                if (!temp.Equals(_connected))
                {
                    _connected = temp;
                    try
                    {
                        if (!_connected)
                        {
                            foreach (KeyValuePair<string, BaseDevProtocol> devProtocol in protocols)
                            {
                                devProtocol.Value.Dev.IsConnected = false;
                                devProtocol.Value.DataItems.ForEach(sss => sss.CurValue = 0);
                            }
                        }
                    }
                    catch 
                    {
                    }
                }
                return _connected;
            }
        }
        
        public override bool Running
        {
            get => _running;
            set
            {
                if (!_running.Equals(value))
                {
                    _running = value;
                }
            }
        }

        public override bool Start()
        {
            try
            {
                if (!Connected)
                {
                    Stop();
                    Connect(true);
                    _channelThread = new Thread(ThreadProc);
                    _channelThread.IsBackground = true;
                    Running = true;
                    _channelThread.Start();
                }
                else
                {
                    Running = true;
                }
            }
            catch (Exception ex)
            {
                _log.Error(ex.Message);
                Running = false;
                return false;
            }
            return true;
        }
        public override void Stop()
        {
            try
            {
                Running = false;
                _channelThread?.Abort();
                ClosePort();
                base.Stop();
            }
            catch //(Exception e)
            {
                //_log.Error(e.Message);
            }
        }

        protected override bool Connect(bool log = false)
        {
            try
            {
                if (!SerialPort.GetPortNames().Contains(ChlInfo.Comport))
                    return false;
                if (_comPort == null)
                    _comPort = new SerialPort();
                _comPort.PortName = ChlInfo.Comport;
                _comPort.BaudRate = ChlInfo.Baudrate;
                _comPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), ChlInfo.StopBits);
                _comPort.DataBits = ChlInfo.DataBits;
                _comPort.Parity = (Parity)Enum.Parse(typeof(Parity), ChlInfo.Parity);
                _comPort.ReadTimeout = 25;

                if (_comPort.IsOpen)
                {
                    _comPort.Close();
                    _comPort.Open();
                }
                else
                {
                    _comPort.Open();
                }
                if (!_comPort.IsOpen && log)
                    _log.Error($"{ChlInfo.Name} {_comPort.PortName}打开失败");
                else if (_comPort.IsOpen && !log)
                    _log.Info($"{ChlInfo.Name} {_comPort.PortName}打开成功");
                _comPort.DataReceived += Comport_DataReceived;
            }
            catch
            {
                if (_comPort != null && !_comPort.IsOpen && log)
                    _log.Error($"{ChlInfo.Name} {_comPort.PortName}打开失败");
                return false;
            }
            return true;
        }

        public override void PutCommand(RxTxCommand cmd)
        {
            if (cmd != null)
            {
                _sendCommands.Enqueue(cmd, 100);
            }
        }

        public override void PutInsertCommand(RxTxCommand cmd)
        {
            if (cmd != null)
            {
                _insertCommands.Enqueue(cmd, 100);
            }
        }

        public override RxTxCommand PostToExcute(string devId, string operId)
        {
            if (protocols.ContainsKey(devId))
            {
                RxTxCommand cmd = protocols[devId].PostToExcute(operId);
                PutCommand(cmd);
                return cmd;
            }
            return null;
        }

        public override RxTxCommand PostToExcute(string devId, string operId, string[] paras)
        {
            if (protocols.ContainsKey(devId))
            {
                RxTxCommand cmd = protocols[devId].PostToExcute(operId, paras);
                PutCommand(cmd);
                return cmd;
            }
            return null;
        }

        

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="cmd"></param>
        /// <returns></returns>
        public bool SendData(RxTxCommand cmd)
        {
            if (cmd == null || cmd.SendBytes == null || cmd.SendBytes.Length==0) return false;
            byte[] byData = cmd.SendBytes;
           
            try
            {
                _comPort.DiscardInBuffer();
                _comPort.DiscardOutBuffer();
                _comPort.Write(byData, 0, byData.Length);
            }
            catch 
            {
            }
            return true;
        }
        /// <summary>
        /// 取出接收缓冲区数据
        /// </summary>
        /// <returns></returns>
        public  byte[] ReadData()
        {
            List<byte> _recBytelist = new List<byte>();
            int iRec = 0;
            if (_comPort.ReadTimeout < 10)
            {
                _comPort.ReadTimeout = 25;
            }

            try
            {
                while (iRec != -1 && _recBytelist.Count < 1000) // 测试发现没有出现-1  而是最后读取超时
                {
                    iRec = _comPort.ReadByte(); // 超时会引发异常（接收第一个字节后超时,作为结束判断）
                    _recBytelist.Add((byte)iRec);
                }
            }
            catch  // 等待超时
            {
                if (_recBytelist.Count > 0)
                {
                    return _recBytelist.ToArray();
                }
                else
                {
                    return null;
                }
            }
            if (_recBytelist.Count > 0)
            {
                return _recBytelist.ToArray();
            }
            else
            {
                return null;
            }
        }


    }
}
